package com.qz.mos.filter;

import com.alibaba.fastjson.JSON;
import com.qz.mos.Mos2Application;
import com.qz.mos.contorller.RestUtil;
import com.qz.mos.entity.RestData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.GZIPInputStream;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

@Component
@Slf4j
@Order( Integer.MIN_VALUE )
public class MyLogFilter implements GatewayFilter, Ordered {


    @Override
    public Mono <Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        RestData restData = new RestData();
        ServerHttpRequest request = exchange.getRequest();
        addOriginalRequestUrl( exchange, request.getURI() );

        String method = request.getMethodValue();
        restData.setMethod( method );
        restData.setHeadersData( JSON.toJSONString( request.getHeaders() ));
        restData.setUrl( request.getPath().toString() );
        if ("POST".equals( method )) {
            return DataBufferUtils.join( exchange.getRequest().getBody() )
                    .flatMap( dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read( bytes );
                        try {
                            String bodyString = new String( bytes, "utf-8" );
                            restData.setBody( bodyString );
                            log.info( bodyString );//打印请求参数

                            Mos2Application.restDataThreadLocal.set(  restData);

                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        DataBufferUtils.release( dataBuffer );
                        Flux <DataBuffer> cachedFlux = Flux.defer( () -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap( bytes );
                            return Mono.just( buffer );
                        } );

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest() ) {
                            @Override
                            public Flux <DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator( exchange, restData );
                        return chain.filter( exchange.mutate().request( mutatedRequest ).response( decoratedResponse )
                                .build() );
                    } );
        }
        ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator( exchange, restData );
        return chain.filter( exchange.mutate().response( decoratedResponse ).build() );
    }

    protected String getMediaType(ServerHttpRequest request, ServerHttpResponse originalResponse) {
        String result;
        MediaType contentType = originalResponse.getHeaders().getContentType();
        if (contentType != null) {
            result = contentType.toString();
        } else {
            contentType = request.getHeaders().getContentType();
            if (contentType != null) {
                result = contentType.toString();
            } else {
                result = MediaTypeFactory.getMediaType( request.getPath().toString() ).orElse( MediaType.ALL ).toString();
            }

        }

        return result;
    }

    private ServerHttpResponseDecorator getServerHttpResponseDecorator(ServerWebExchange exchange, RestData restData) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        String mediaType = getMediaType( request, originalResponse );
        restData.setRespType( mediaType );
        return new ServerHttpResponseDecorator( originalResponse ) {
            @Override
            public Mono <Void> writeWith(Publisher <? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux <? extends DataBuffer> fluxBody = (Flux <? extends DataBuffer>) body;
                    return super.writeWith( fluxBody.buffer().map( dataBuffer -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join( dataBuffer );
                        byte[] content = new byte[join.readableByteCount()];
                        join.read( content );
                        //释放掉内存
                        DataBufferUtils.release( join );
                        String s = new String( content, StandardCharsets.UTF_8 );

                        List <String> strings = exchange.getResponse().getHeaders().get( HttpHeaders.CONTENT_ENCODING );
                        if (!CollectionUtils.isEmpty( strings ) && strings.contains( "gzip" )) {
                            GZIPInputStream gzipInputStream = null;
                            try {
                                gzipInputStream = new GZIPInputStream( new ByteArrayInputStream( content ), content.length );
                                StringWriter writer = new StringWriter();
                                IOUtils.copy( gzipInputStream, writer, "UTF-8" );
                                s = writer.toString();

                            } catch (IOException e) {
                                log.error( "====Gzip IO error", e );
                            } finally {
                                if (gzipInputStream != null) {
                                    try {
                                        gzipInputStream.close();
                                    } catch (IOException e) {
                                        log.error( "===Gzip IO close error", e );
                                    }
                                }
                            }
                        } else {
                            s = new String( content, StandardCharsets.UTF_8 );
                        }
                        if (s.contains( "�" )) {
                            restData.setResBody( content );
                        } else {
                            restData.setResBody( s );
                        }


                        RestUtil.restDataService.save( restData );
                        return bufferFactory.wrap( content );
                    } ) );
                } else {
                    Flux <? extends DataBuffer> fluxBody = (Flux <? extends DataBuffer>) body;
                    return super.writeWith( fluxBody.buffer().map( dataBuffer -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join( dataBuffer );
                        byte[] content = new byte[join.readableByteCount()];
                        join.read( content );
                        //释放掉内存
                        DataBufferUtils.release( join );
                        restData.setResBody( content );
                        RestUtil.restDataService.save( restData );
                        return bufferFactory.wrap( content );
                    } ) );
                }
            }
        };
    }

    @Override
    public int getOrder() {
        return -200;
    }

    public MyLogFilter() {
    }

    public MyLogFilter(Object config) {
        System.out.println( config );
    }
}